package org.hibernate.mapping;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public class TableWrapper {

	private final Table table;
	private final Class<?> entityClass;

	private List<ColumnWrapper> pkColumnWrapper = new ArrayList<>();
	private List<ColumnWrapper> columnWrappers = new ArrayList<>();

	public void addPKColumnWrapper(final ColumnWrapper columnWrapper) { this.pkColumnWrapper.add(columnWrapper); }
	public void addColumnWrapper(final ColumnWrapper columnWrapper) { this.columnWrappers.add(columnWrapper); }

	// 因Hibernate并未提供setter方法来改变，原则上还是不应改变源码来实现。
	// 所以需要完全克隆一个Table传递给父类方法使用。
	// 那就需要明确源码的逻辑。
	// 本方法目前是建立在5.6.8.Final基础上，如后续版本升级，需要确保使用逻辑并未更改。
	@SuppressWarnings("unchecked")
	public Table sortedColumnsTable() {
		final Table t = new Table();

		// 49 Identifier catalog
		t.setCatalog(this.table.getCatalog());
		// 50 Identifier schema
		t.setSchema(this.table.getSchema());
		// 51 Identifier name
		t.setName(this.table.getName());

		// 56 Map<String, Column> columns
		this.pkColumnWrapper.stream().sorted((o1, o2) -> { return o1.getOrder().compareTo(o2.getOrder()); }).collect(Collectors.toList()).forEach(cw -> {
			this.table.getColumnIterator().forEachRemaining(c -> {
				if (cw.getColumn().equals(c)) { t.addColumn(c); }
				return;
			});
		});
		this.columnWrappers.stream().sorted((o1, o2) -> { return o1.getOrder().compareTo(o2.getOrder()); }).collect(Collectors.toList()).forEach(cw -> {
			this.table.getColumnIterator().forEachRemaining(c -> {
				if (cw.getColumn().equals(c)) { t.addColumn(c); }
				return;
			});
		});
		// 57 KeyValue idValue
		t.setIdentifierValue(this.table.getIdentifierValue());
		// 58 PrimaryKey primaryKey
		t.setPrimaryKey(this.table.getPrimaryKey());
		// 59 Map<ForeignKeyKey, ForeignKey> foreignKeys
		final List<ForeignKey> foreignKeys = new ArrayList<>();
		this.table.getForeignKeyIterator().forEachRemaining(foreignKeys::add);
		foreignKeys.forEach(fk -> { t.createForeignKey(fk.getName(), fk.getColumns(), fk.getReferencedEntityName(), fk.getKeyDefinition(), fk.getReferencedColumns()); });
		// 60 Map<String, Index> indexes
		final List<Index> indexes = new ArrayList<>();
		this.table.getIndexIterator().forEachRemaining(indexes::add);
		indexes.forEach(t::addIndex);
		// 61 Map<String,UniqueKey> uniqueKeys
		final List<UniqueKey> uniqueKeys = new ArrayList<>();
		this.table.getUniqueKeys().forEach((key, uk) -> { uniqueKeys.add(uk); });
		uniqueKeys.forEach(t::addUniqueKey);
		// 62 int uniqueInteger
		t.setUniqueInteger(this.table.getUniqueInteger());
		// 63 List<String> checkConstraints
		final List<String> checkConstraints = new ArrayList<>();
		this.table.getCheckConstraintsIterator().forEachRemaining(checkConstraints::add);
		checkConstraints.forEach(t::addCheckConstraint);
		// 64 String rowId
		t.setRowId(this.table.getRowId());
		// 65 String subselect
		t.setSubselect(this.table.getSubselect());
		// 66 boolean isAbstract
		t.setAbstract(this.table.isAbstract());
		// 67 boolean hasDenormalizedTables
		if (this.table.hasDenormalizedTables()) { t.setHasDenormalizedTables(); }
		// 68 String comment
		t.setComment(this.table.getComment());

		return t;
	}

	// 唯一键约束使用@Column(unique = true)缺失，原因是
	// org.hibernate.tool.schema.internal.StandardTableExporter[109]#getSqlCreateStrings方法中调用了Table.getOrCreateUniqueKey(String)方法
	// 在这个方法调用之前，Table中并未创建该类型的唯一键约束，因此当前方法克隆的Table中就没有该类型的唯一键约束。
	// 虽然克隆后的Table传入后创建了该类型的唯一键约束，但是Hibernate在
	// org.hibernate.tool.schema.internal.GroupedSchemaMigratorImpl[74]行才进入的如上逻辑处理，然而这一步仅创建了表结构，并未为表创建索引及唯一键约束，
	// 索引语句没有此特殊情况，没有问题。唯一键约束语句则在同方法的第90行中调用，可以看到，目前版本的调用，使用的Table仍然是从namespace中取的。
	// 如上所述，虽然克隆过程没有问题，建表语句也为克隆后的table动态创建了该类型的唯一键约束，但在实际的创建唯一键约束语句时，仍然使用的是原有table，因而该类型的唯一键约束就丢失了。
	// 那么我们可以按照同样的方法为原始table创建该类型唯一键约束。它需要依赖条件很多，且还是使用到一组源码，而非开放的功能，不推荐。
	// 我们可以在调用完成后，使用克隆且被改变的table中的值来将增加的值加到原始table上，这样做更安全可靠，不太容易受版本升级影响。
	public static void afterGetSqlCreateStrings(final Table table, final Table sortedColumnsTable) {
		final Map<String, UniqueKey> tableUniqueKeys = table.getUniqueKeys();
		final Map<String, UniqueKey> sortedColumnsTableUniqueKeys = sortedColumnsTable.getUniqueKeys();
		if (tableUniqueKeys.size() == sortedColumnsTableUniqueKeys.size()) { return; }
		sortedColumnsTableUniqueKeys.forEach((key, uk) -> {
			if (!tableUniqueKeys.containsKey(key)) { table.addUniqueKey(uk); }
		});
	}

}
